#include <jni.h>
#include <logUtil.h>
#include "WhiteboardEngine.h"
#include "shader/SquareShader.h"
#include "shader/TextureImageDemo.h"
#include <android/bitmap.h>
#include <malloc.h>
#include <string.h>
#include <queue>
#include <stdio.h>
#include <list>


WhiteboardEngine *whiteboardEngine = nullptr;

TextureImageDemo *bgShader = nullptr;

JavaVM *mJavaVM;
jobject dataCallBack;
int mNeedDetach = 0;

ImageInfo *bitmapToImageInfo(JNIEnv *env, jobject &bitmap) {
    AndroidBitmapInfo info; // create a AndroidBitmapInfo
    int result;
    // 获取图片信息
    result = AndroidBitmap_getInfo(env, bitmap, &info);
    if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
        LOGCATE("AndroidBitmap_getInfo failed, result: %d", result);
        return nullptr;
    }
    // 获取像素信息
    unsigned char *data;
    result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void **>(&data));
    if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
        LOGCATE("AndroidBitmap_lockPixels failed, result: %d", result);
        return nullptr;
    }
    size_t count = info.stride * info.height;

    unsigned char *resultData = (unsigned char *) malloc(count * sizeof(unsigned char));
    memcpy(resultData, data, count);

    // 像素信息不再使用后需要解除锁定
    result = AndroidBitmap_unlockPixels(env, bitmap);
    if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
        LOGCATE("AndroidBitmap_unlockPixels failed, result: %d", result);
    }

    ImageInfo *brushPoints = new ImageInfo(info.width, info.height, resultData);
    return brushPoints;
}


extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_init(JNIEnv *env, jclass clazz, jint width,
                                         jint height, jobject surface, jobject bitmap,
                                         jobject jCallback) {
    if (whiteboardEngine != nullptr) {
        delete whiteboardEngine;
        whiteboardEngine = nullptr;
    }

    //初始化EGL
    whiteboardEngine = new WhiteboardEngine(surface, env);
    whiteboardEngine->init(width, height, bitmapToImageInfo(env, bitmap), env, jCallback);
}

extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glDrawPaint(JNIEnv *env, jclass instance, jfloatArray point,
                                                jint verTextSize, jfloat calulateXYAnagle) {

    if (whiteboardEngine == nullptr) return;

    jfloat *pDouble = env->GetFloatArrayElements(point, nullptr);
    whiteboardEngine->glDrawPoints(pDouble, verTextSize, calulateXYAnagle);
}


extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glResultMatrix(JNIEnv *env, jclass instance,
                                                   jfloatArray matrixValue) {
    if (whiteboardEngine == nullptr) return;

    jfloat *f = env->GetFloatArrayElements(matrixValue, nullptr);
    whiteboardEngine->glResultMatrix(f);
}



extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glResult(JNIEnv *env, jclass instance, jfloat r, jfloat restR,
                                             jfloat dx, jfloat dy, jfloat sc) {
    if (whiteboardEngine == nullptr) return;
    whiteboardEngine->glResult(r, restR, dx, dy, sc);
}

extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glSetPaintTexture(JNIEnv *env, jclass instance,
                                                      jobject bitmap, jboolean isTextureRotate,
                                                      jfloat brushWidth,
                                                      jint outType) {
    if (whiteboardEngine == nullptr) return;

    BrushInfo::OutType setOutType;
    switch (outType) {
        case 1:
            setOutType = BrushInfo::OutType::ERASER;
            break;
        case 2:
            setOutType = BrushInfo::OutType::FILL;
            break;
        default:
            setOutType = BrushInfo::OutType::DRAW;
            break;
    }
    //不等于空说明需要重新设置纹理图片
    if (bitmap != nullptr) {
        ImageInfo *pInfo = bitmapToImageInfo(env, bitmap);

        whiteboardEngine->glSetPaintTexture(pInfo, brushWidth, isTextureRotate,
                                            setOutType);
    } else {
        whiteboardEngine->glSetPaintTexture(nullptr, brushWidth, isTextureRotate,
                                            setOutType);
    }
}

extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glDrawData(JNIEnv *env, jclass instance, jfloatArray point,
                                               jint verTextSize,
                                               jobject bitmap, jfloat brushWidth,
                                               jint outType,
                                               jboolean isTextureRotate,
                                               jfloat A,
                                               jfloat R,
                                               jfloat G,
                                               jfloat B,
                                               jboolean isClear,
                                               jboolean isDisplay) {
    if (whiteboardEngine == nullptr) return;

    BrushInfo::OutType setOutType;
    switch (outType) {
        case 1:
            setOutType = BrushInfo::OutType::ERASER;
            break;
        case 2:
            setOutType = BrushInfo::OutType::FILL;
            break;
        default:
            setOutType = BrushInfo::OutType::DRAW;
            break;
    }

    jfloat *pDouble = point == nullptr ? nullptr : env->GetFloatArrayElements(point, nullptr);

    //不等于空说明需要重新设置纹理图片
    if (bitmap != nullptr) {
        ImageInfo *pInfo = bitmapToImageInfo(env, bitmap);
        whiteboardEngine->glDrawData(pDouble, verTextSize, pInfo, brushWidth, setOutType,
                                     isTextureRotate, isClear,
                                     isDisplay, A, R, G, B);
    } else {
        whiteboardEngine->glDrawData(pDouble, verTextSize, nullptr, brushWidth, setOutType,
                                     isTextureRotate, isClear,
                                     isDisplay, A, R, G, B);
    }
}



extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glPaintColor(JNIEnv *env, jclass instance, jfloat a, jfloat r,
                                                 jfloat g, jfloat b) {
    if (whiteboardEngine == nullptr) return;

    whiteboardEngine->setPaintColor(a, r, g, b);

}



extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glTranslate(JNIEnv *env, jclass instance, jfloat dx,
                                                jfloat dy) {
    if (whiteboardEngine == nullptr) return;
    whiteboardEngine->glTranslate(dx, dy);
}

extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glScale(JNIEnv *env, jclass instance, jfloat sc) {
    if (whiteboardEngine == nullptr) return;
    whiteboardEngine->glScale(sc);
}
extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glRotate(JNIEnv *env, jclass instance, jfloat sc) {
    if (whiteboardEngine == nullptr) return;
    whiteboardEngine->glRotate(sc);
}



extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_onDestroy(JNIEnv *env, jclass instance) {
    if (whiteboardEngine != nullptr) {
        delete whiteboardEngine;
        whiteboardEngine = nullptr;
    }
}

extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glClearAll(JNIEnv *env, jclass instance) {
    if (whiteboardEngine != nullptr) {
        whiteboardEngine->glClearColor();
    }
}
extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glClearPaintColor(JNIEnv *env, jclass instance) {
    if (whiteboardEngine != nullptr) {
        whiteboardEngine->glClearPaint();
    }
}



extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glInit(JNIEnv *env, jclass instance, jint w, jint h,
                                           jobject bitmap) {
    ImageInfo *pInfo = bitmapToImageInfo(env, bitmap);
    bgShader = new TextureImageDemo();

    bgShader->imaData = pInfo->pixels;
    bgShader->imgWidth = pInfo->width;
    bgShader->imgHeight = pInfo->height;

    bgShader->Init();
    bgShader->OnSurfaceChanged(w, h);
}


extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glTestDraw(JNIEnv *env, jclass instance) {
    bgShader->draw();
}

extern "C" JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glTestDrawR(JNIEnv *env, jclass instance, jfloat x, jfloat y,
                                                jfloat z) {
    bgShader->change(x, y, z);
}


char *jstringToChar(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("utf-8");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}


extern "C"
JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glSave(JNIEnv *env, jclass clazz, jstring savePath,
                                           jobject callBack) {
    if (whiteboardEngine == nullptr) return;
    char *charSavePath = jstringToChar(env, savePath);

    whiteboardEngine->glSave(env, charSavePath, callBack);
}
extern "C" {
bool isPixelValid(int currentColor, int oldColor, int *startColor, int tolerance);

void floodFillColor(uint32_t x,
                    uint32_t y,
                    uint32_t fillColor,
                    uint32_t *bitmapPixels,
                    uint32_t width,
                    uint32_t height,
                    uint32_t tolerance,
                    std::list<int> *xArray,
                    std::list<int> *yArray);

void nativePointsCallBack(jobject pointsCallBack,int arrayLen, float * points);

}

void nativePointsCallBack(jobject pointsCallBack,int arrayLen, float* points) {
    JNIEnv *mEnvs = nullptr;
    int getEnvStat = mJavaVM->GetEnv((void **) &mEnvs, JNI_VERSION_1_6);
    //获取当前native线程是否有没有被附加到jvm环境中
    if (getEnvStat == JNI_EDETACHED) {
        //如果没有， 主动附加到jvm环境中，获取到env
        if (mJavaVM->AttachCurrentThread(&mEnvs, NULL) != 0) {
            return;
        }
        mNeedDetach = JNI_TRUE;
    }



    jclass javaClass = mEnvs->GetObjectClass(pointsCallBack);

    if (javaClass == 0) {
        LOGCATE("Unable to find javaClass == 0")
        return;
    }

    //获取要回调的方法ID
    jmethodID javaCallbackId = mEnvs->GetMethodID(javaClass, "pointsCallBack", "([F)V");
    if (javaCallbackId == NULL) {
        LOGCATE("Unable to find method:test")
        return;
    }

    jfloatArray ret = mEnvs->NewFloatArray(arrayLen);
    mEnvs->SetFloatArrayRegion(ret, 0, arrayLen, points);

    mEnvs->CallVoidMethod(pointsCallBack, javaCallbackId,ret);
    if (mEnvs != nullptr && pointsCallBack != nullptr)
        mEnvs->DeleteGlobalRef(pointsCallBack);

    if (mNeedDetach) {
        mJavaVM->DetachCurrentThread();
    }

    mNeedDetach = 0;
    mJavaVM = nullptr;
    mEnvs = nullptr;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_immcque_paint_ShaderNative_glFillColor(JNIEnv *env, jclass clazz,
                                                jfloat x, jfloat y,
                                                jint fillColor, jint tolerance,
                                                jobject pointsCallBack) {
    if (whiteboardEngine == nullptr) return;
    if (whiteboardEngine->isDraw()) return;
    int width = whiteboardEngine->resultShader->getSurfaceWidth();
    int height = whiteboardEngine->resultShader->getSurfaceHeight();

    int bufferLen = width * height * 4;
    auto *buffer = new unsigned char[bufferLen];

    //JavaVM是虚拟机在JNI中的表示，等下再其他线程回调java层需要用到
    if (mJavaVM == nullptr)
        env->GetJavaVM(&mJavaVM);
    if (dataCallBack != nullptr)
        dataCallBack = nullptr;
    //生成一个全局的callBack
    dataCallBack = env->NewGlobalRef(pointsCallBack);

    whiteboardEngine->getBuffer(buffer, [=] {
        auto *tempPixels = new uint32_t[bufferLen / 4];
        for (int i = 0; i < bufferLen; i += 4) {
            int R = buffer[i];
            int G = buffer[i + 1];
            int B = buffer[i + 2];
            int A = buffer[i + 3];
            uint32_t color = ((A << 24) & 0xFF000000) | ((B << 16) & 0xFF0000) |
                             ((G << 8) & 0x00FF00) |
                             (R & 0x0000FF);
            tempPixels[i / 4] = color;
        }
        auto *bitmapPixels = new uint32_t[bufferLen / 4];
        int pixelsLen = bufferLen / 4;
        for (int i = 0; i < pixelsLen; ++i) {
            int h = (height - 1) - (i / width);
            int w = i % width;
            bitmapPixels[i] = tempPixels[h * width + w];
        }
        delete[] tempPixels;
        delete[] buffer;

        std::list<int> xArray;
        std::list<int> yArray;

        int cx = x;
        int cy = y;
        uint32_t cFillColor = fillColor;
        floodFillColor(cx, cy, cFillColor, bitmapPixels, width, height,
                       tolerance, &xArray, &yArray);

        int arrayLen = (int) xArray.size() * 2;

        LOGCATE("arrayLen = %d", arrayLen);

        auto *points = (float *) malloc(sizeof(float) * arrayLen);
        float nx, ny;
        for (int i = 0; i < arrayLen; i += 2) {
            nx = (float) xArray.front() / width * 2 - 1;
            if (nx > 1) {
                nx = 1;
            } else if (nx < -1) {
                nx = -1;
            }
            points[i] = nx;
            xArray.pop_front();

            ny = 1 - (float) yArray.front() / height * 2;
            if (ny > 1) {
                ny = 1;
            } else if (ny < -1) {
                ny = -1;
            }
            points[i + 1] = ny;
            yArray.pop_front();
        }
        nativePointsCallBack(dataCallBack,arrayLen, points);
        while (!xArray.empty()) {
            xArray.clear();
            yArray.clear();
        }
        // 释放内存
        delete[] points;
        delete[] bitmapPixels;
    });

}

bool isPixelValid(int currentColor, int oldColor, int *startColor, int tolerance) {

    if (tolerance != 0) {
        int alpha = ((currentColor & 0xFF000000) >> 24);
        int red = ((currentColor & 0xFF0000) >> 16) * alpha / 255; // red
        int green = ((currentColor & 0x00FF00) >> 8) * alpha / 255; // Green
        int blue = (currentColor & 0x0000FF) * alpha / 255; // Blue

        return (red >= (startColor[0] - tolerance)
                && red <= (startColor[0] + tolerance)
                && green >= (startColor[1] - tolerance)
                && green <= (startColor[1] + tolerance)
                && blue >= (startColor[2] - tolerance)
                && blue <= (startColor[2] + tolerance));
    } else {
        if (currentColor == oldColor) {
            return true;
        } else {
            return false;
        }
    }
}

void floodFillColor(uint32_t x,
                    uint32_t y,
                    uint32_t color,
                    uint32_t *bitmapPixels,
                    uint32_t width,
                    uint32_t height,
                    uint32_t tolerance,
                    std::list<int> *xArray,
                    std::list<int> *yArray) {


    int values[3] = {};

    if (x > width - 1)
        return;
    if (y > height - 1)
        return;
    if (x < 0)
        return;
    if (y < 0)
        return;

    uint32_t oldColor;

    int red = 0;
    int blue = 0;
    int green = 0;
    int alpha = 0;
    oldColor = bitmapPixels[y * width + x];

    // Used to hold the the start( touched ) color that we like to change/fill
    if (color == oldColor)
        return;

    // Get red,green and blue values of the old color we like to change
    alpha = (int) ((color & 0xFF000000) >> 24);

    values[0] = (int) ((oldColor & 0xFF0000) >> 16) * alpha / 255; // red
    values[1] = (int) ((oldColor & 0x00FF00) >> 8) * alpha / 255; // Green
    values[2] = (int) (oldColor & 0x0000FF) * alpha / 255; // Blue


    alpha = (int) ((color & 0xFF000000) >> 24);
    blue = (int) ((color & 0xFF0000) >> 16);
    green = (int) ((color & 0x00FF00) >> 8);
    red = (int) (color & 0x0000FF);
    blue = blue * alpha / 255;
    green = green * alpha / 255;
    red = red * alpha / 255;

    int tmp = 0;
    tmp = red;
    red = blue;
    blue = tmp;

    color = ((alpha << 24) & 0xFF000000) | ((blue << 16) & 0xFF0000) |
            ((green << 8) & 0x00FF00) |
            (red & 0x0000FF);

    LOGCATD("edit1");
    std::queue<uint32_t> pixelsX;
    std::queue<uint32_t> pixelsY;

    int nx = 0;
    int ny = 0;
    int wx = 0;
    int wy = 0;
    int ex = 0;
    int ey = 0;

    pixelsX.push(x);
    pixelsY.push(y);

    while (!pixelsX.empty()) {

        nx = pixelsX.front();
        ny = pixelsY.front();
        pixelsX.pop();
        pixelsY.pop();

        if (bitmapPixels[ny * width + nx] == color)
            continue;

        wx = nx;
        wy = ny;
        ex = wx + 2;
        ey = wy;

        while (wx > 0 &&
               isPixelValid(bitmapPixels[wy * width + wx], oldColor, values, tolerance)) {
            bitmapPixels[wy * width + wx] = color;

            xArray->push_back(wx);
            yArray->push_back(wy);
            if (wy-1 > 0 && isPixelValid(bitmapPixels[(wy - 2) * width + wx], oldColor, values,
                                       tolerance)) {
                pixelsX.push(wx);
                pixelsY.push(wy - 2);
            }
            if (wy+1 < height - 1 &&
                isPixelValid(bitmapPixels[(wy + 2) * width + wx], oldColor, values,
                             tolerance)) {
                pixelsX.push(wx);
                pixelsY.push(wy + 2);
            }
            wx-=2;
        }

        while (ex < width - 1 &&
               isPixelValid(bitmapPixels[ey * width + ex], oldColor, values, tolerance)) {
            bitmapPixels[ey * width + ex] = color;
            xArray->push_back(ex);
            yArray->push_back(ey);
            if (ey-1 > 0 && isPixelValid(bitmapPixels[(ey - 2) * width + ex], oldColor, values,
                                       tolerance)) {
                pixelsX.push(ex);
                pixelsY.push(ey - 2);
            }
            if (ey+1 < height - 1 &&
                isPixelValid(bitmapPixels[(ey + 2) * width + ex], oldColor, values,
                             tolerance)) {
                pixelsX.push(ex);
                pixelsY.push(ey + 2);
            }
            ex+=2;
        }

        int size = pixelsX.size();

        LOGCATE("SIZE %d", size);
    }
}

